/*
 * Copyright 2003,2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.easyaop.commons.utils;

import java.util.Arrays;
import java.util.List;

import org.objectweb.asm.Type;

import com.easyaop.commons.ASMOpcodes;
import com.easyaop.commons.Constants;
import com.easyaop.commons.asm.Signature;

public class TypeUtils {
    public static Type getType(String className) {
        return Type.getType("L" + className.replace('.', '/') + ";");
    }

    public static Type getType(Class<?> target) {
        return Type.getType(target);
    }

    public static boolean isFinal(int access) {
        return (ASMOpcodes.ACC_FINAL & access) != 0;
    }

    public static boolean isStatic(int access) {
        return (ASMOpcodes.ACC_STATIC & access) != 0;
    }

    public static boolean isProtected(int access) {
        return (ASMOpcodes.ACC_PROTECTED & access) != 0;
    }

    public static boolean isPublic(int access) {
        return (ASMOpcodes.ACC_PUBLIC & access) != 0;
    }

    public static boolean isAbstract(int access) {
        return (ASMOpcodes.ACC_ABSTRACT & access) != 0;
    }

    public static boolean isInterface(int access) {
        return (ASMOpcodes.ACC_INTERFACE & access) != 0;
    }

    public static boolean isPrivate(int access) {
        return (ASMOpcodes.ACC_PRIVATE & access) != 0;
    }

    public static boolean isSynthetic(int access) {
        return (ASMOpcodes.ACC_SYNTHETIC & access) != 0;
    }

    public static boolean isBridge(int access) {
        return (ASMOpcodes.ACC_BRIDGE & access) != 0;
    }

    public static Type[] add(Type[] types, Type extra) {
        if (types == null) {
            return new Type[]{extra};
        } else {
            List list = Arrays.asList(types);
            if (list.contains(extra)) {
                return types;
            }
            Type[] copy = new Type[types.length + 1];
            System.arraycopy(types, 0, copy, 0, types.length);
            copy[types.length] = extra;
            return copy;
        }
    }

    public static Type[] add(Type[] t1, Type[] t2) {
        // TODO: set semantics?
        Type[] all = new Type[t1.length + t2.length];
        System.arraycopy(t1, 0, all, 0, t1.length);
        System.arraycopy(t2, 0, all, t1.length, t2.length);
        return all;
    }

    public static Type fromInternalName(String name) {
        // TODO; primitives?
        return Type.getType("L" + name + ";");
    }

    public static Type[] fromInternalNames(String[] names) {
        if (names == null) {
            return null;
        }
        Type[] types = new Type[names.length];
        for (int i = 0; i < names.length; i++) {
            types[i] = fromInternalName(names[i]);
        }
        return types;
    }

    public static int getStackSize(Type[] types) {
        int size = 0;
        for (int i = 0; i < types.length; i++) {
            size += types[i].getSize();
        }
        return size;
    }

    public static String[] toInternalNames(Type[] types) {
        if (types == null) {
            return null;
        }
        String[] names = new String[types.length];
        for (int i = 0; i < types.length; i++) {
            names[i] = types[i].getInternalName();
        }
        return names;
    }

    public static Type getBoxedType(Type type) {
        switch (type.getSort()) {
            case Type.CHAR:
                return Constants.T_CHARACTER;
            case Type.BOOLEAN:
                return Constants.T_BOOLEAN;
            case Type.DOUBLE:
                return Constants.T_DOUBLE;
            case Type.FLOAT:
                return Constants.T_FLOAT;
            case Type.LONG:
                return Constants.T_LONG;
            case Type.INT:
                return Constants.T_INTEGER;
            case Type.SHORT:
                return Constants.T_SHORT;
            case Type.BYTE:
                return Constants.T_BYTE;
            default:
                return type;
        }
    }

    public static Type getUnboxedType(Type type) {
        if (Constants.T_INTEGER.equals(type)) {
            return Type.INT_TYPE;
        } else if (Constants.T_BOOLEAN.equals(type)) {
            return Type.BOOLEAN_TYPE;
        } else if (Constants.T_DOUBLE.equals(type)) {
            return Type.DOUBLE_TYPE;
        } else if (Constants.T_LONG.equals(type)) {
            return Type.LONG_TYPE;
        } else if (Constants.T_CHARACTER.equals(type)) {
            return Type.CHAR_TYPE;
        } else if (Constants.T_BYTE.equals(type)) {
            return Type.BYTE_TYPE;
        } else if (Constants.T_FLOAT.equals(type)) {
            return Type.FLOAT_TYPE;
        } else if (Constants.T_SHORT.equals(type)) {
            return Type.SHORT_TYPE;
        } else {
            return type;
        }
    }

    public static boolean isArray(Type type) {
        return type.getSort() == Type.ARRAY;
    }

    public static Type getComponentType(Type type) {
        if (!isArray(type)) {
            throw new IllegalArgumentException("Type " + type + " is not an array");
        }
        return Type.getType(type.getDescriptor().substring(1));
    }

    public static boolean isPrimitive(Type type) {
        switch (type.getSort()) {
            case Type.ARRAY:
            case Type.OBJECT:
                return false;
            default:
                return true;
        }
    }

    public static Type[] getTypes(Class[] classes) {
        if (classes == null) {
            return null;
        }
        Type[] types = new Type[classes.length];
        for (int i = 0; i < classes.length; i++) {
            types[i] = Type.getType(classes[i]);
        }
        return types;
    }

    public static int ICONST(int value) {
        switch (value) {
            case -1:
                return ASMOpcodes.ICONST_M1;
            case 0:
                return ASMOpcodes.ICONST_0;
            case 1:
                return ASMOpcodes.ICONST_1;
            case 2:
                return ASMOpcodes.ICONST_2;
            case 3:
                return ASMOpcodes.ICONST_3;
            case 4:
                return ASMOpcodes.ICONST_4;
            case 5:
                return ASMOpcodes.ICONST_5;
        }
        return -1; // error
    }

    public static int LCONST(long value) {
        if (value == 0L) {
            return ASMOpcodes.LCONST_0;
        } else if (value == 1L) {
            return ASMOpcodes.LCONST_1;
        } else {
            return -1; // error
        }
    }

    public static int FCONST(float value) {
        if (value == 0f) {
            return ASMOpcodes.FCONST_0;
        } else if (value == 1f) {
            return ASMOpcodes.FCONST_1;
        } else if (value == 2f) {
            return ASMOpcodes.FCONST_2;
        } else {
            return -1; // error
        }
    }

    public static int DCONST(double value) {
        if (value == 0d) {
            return ASMOpcodes.DCONST_0;
        } else if (value == 1d) {
            return ASMOpcodes.DCONST_1;
        } else {
            return -1; // error
        }
    }

    public static int NEWARRAY(Type type) {
        switch (type.getSort()) {
            case Type.BYTE:
                return ASMOpcodes.T_BYTE;
            case Type.CHAR:
                return ASMOpcodes.T_CHAR;
            case Type.DOUBLE:
                return ASMOpcodes.T_DOUBLE;
            case Type.FLOAT:
                return ASMOpcodes.T_FLOAT;
            case Type.INT:
                return ASMOpcodes.T_INT;
            case Type.LONG:
                return ASMOpcodes.T_LONG;
            case Type.SHORT:
                return ASMOpcodes.T_SHORT;
            case Type.BOOLEAN:
                return ASMOpcodes.T_BOOLEAN;
            default:
                return -1; // error
        }
    }

    public static String escapeType(String s) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0, len = s.length(); i < len; i++) {
            char c = s.charAt(i);
            switch (c) {
                case '$':
                    sb.append("$24");
                    break;
                case '.':
                    sb.append("$2E");
                    break;
                case '[':
                    sb.append("$5B");
                    break;
                case ';':
                    sb.append("$3B");
                    break;
                case '(':
                    sb.append("$28");
                    break;
                case ')':
                    sb.append("$29");
                    break;
                case '/':
                    sb.append("$2F");
                    break;
                default:
                    sb.append(c);
            }
        }
        return sb.toString();
    }

    public static Type[] parseTypes(Class<?>... classes) {
        if (classes == null || classes.length == 0) {
            return new Type[0];
        }
        Type[] types = new Type[classes.length];
        for (int i = 0; i < classes.length; i++) {
            types[i] = Type.getType(classes[i]);
        }
        return types;
    }

    public static Signature parseConstructor() {
        return new Signature(Constants.NAME_CTOR, Type.getMethodDescriptor(Type.VOID_TYPE));
    }

    public static Signature parseConstructor(Type type0, Type... others) {
        Type[] types = null;
        if (others == null || others.length == 0) {
            types = new Type[]{type0};
        } else {
            types = new Type[others.length + 1];
            types[0] = type0;
            System.arraycopy(others, 0, types, 1, others.length);
        }
        return new Signature(Constants.NAME_CTOR, Type.getMethodDescriptor(Type.VOID_TYPE, types));
    }

    public static Signature parseConstructor(Class<?> class0, Class<?>... others) {
        Type[] types = null;
        if (others == null || others.length == 0) {
            types = new Type[]{Type.getType(class0)};
        } else {
            types = new Type[others.length + 1];
            types[0] = Type.getType(class0);
            for (int i = 0; i < others.length; i++) {
                types[i + 1] = Type.getType(others[i]);
            }
        }
        return new Signature(Constants.NAME_CTOR, Type.getMethodDescriptor(Type.VOID_TYPE, types));
    }
}
